#include "StdAfx.h"
#include "GalconClient.h"
#include <boost/make_shared.hpp>
#include "MessageBuilder.h"
GalconClient::GalconClient(const std::string& address, const std::string& port,
                           GalconClientErrorHandler onConnectFailed, 
                           GalconClientMessageHandler newMessageHandler)
                           : m_connectFailed(onConnectFailed)
                           , m_messageHandler(newMessageHandler)
                           , m_address(address)
                           , m_port(port)
{
}

GalconClient::~GalconClient(void)
{
   m_workingThreadPtr->join();
}

void GalconClient::Connect()
{
   m_ioService = boost::make_shared<boost::asio::io_service>();
   if (m_socket)
   {
      m_socket->close();
   }
   m_socket = boost::make_shared<tcp::socket>(boost::ref(*m_ioService));
   tcp::resolver resolver(*m_ioService);
   m_query = boost::make_shared<tcp::resolver::query>(m_address, m_port);
   m_endpoint_iterator = resolver.resolve(*m_query);
   tcp::endpoint endpoint = *m_endpoint_iterator;
   m_socket->async_connect(endpoint,
      boost::bind(&GalconClient::handleConnect, this,
      boost::asio::placeholders::error, ++m_endpoint_iterator));

   if (m_workingThreadPtr)
   {
      m_workingThreadPtr->interrupt();
      m_workingThreadPtr.reset();
   }
   m_workingThreadPtr = boost::make_shared<boost::thread>(boost::bind(&boost::asio::io_service::run, m_ioService.get()));
}

void GalconClient::Send(const Message& msg)
{
   m_ioService->post(boost::bind(&GalconClient::write, this, msg));
}

void GalconClient::Disconnect()
{
   m_ioService->post(boost::bind(&GalconClient::closeSocket, this));
}

void GalconClient::handleConnect(const boost::system::error_code &error, tcp::resolver::iterator endpoint_iterator)
{
   if (!error)
   {
      boost::asio::async_read(*m_socket,
         boost::asio::buffer(m_message.GetData(), Message::header_length),
         boost::bind(&GalconClient::handle_read_header, this,
         boost::asio::placeholders::error));
   }
   else if (endpoint_iterator != tcp::resolver::iterator())
   {
      closeSocket();
      tcp::endpoint tcp_endpoint = *endpoint_iterator;
      m_socket->async_connect(tcp_endpoint,
         boost::bind(&GalconClient::handleConnect, this,
         boost::asio::placeholders::error, ++m_endpoint_iterator));
   }
   else
   {
      closeSocket();
      m_connectFailed(*MessageBuilder::BuildErrorDescriptionMessage(error));
   }
}

void GalconClient::handle_read_header(const boost::system::error_code& error)
{
   if (!error && m_message.DecodeHeader())
   {
      boost::asio::async_read(*m_socket,
         boost::asio::buffer(m_message.GetBody(), m_message.GetBodyLength()),
         boost::bind(&GalconClient::handle_read_body, this,
         boost::asio::placeholders::error));
   }
   else
   {
      closeSocket();
      m_connectFailed(*MessageBuilder::BuildErrorDescriptionMessage(error));
   }
}

void GalconClient::handle_read_body(const boost::system::error_code& error)
{
   if (!error)
   {
      m_messageHandler(m_message);
      boost::asio::async_read(*m_socket,
         boost::asio::buffer(m_message.GetData(), Message::header_length),
         boost::bind(&GalconClient::handle_read_header, this,
         boost::asio::placeholders::error));
   }
   else
   {
      closeSocket();
      m_connectFailed(*MessageBuilder::BuildErrorDescriptionMessage(error));
   }
}

void GalconClient::closeSocket()
{
   m_socket->close();
}

void GalconClient::write(const Message &msg)
{
   bool writeInProgress = !m_messageDeque.empty();
   m_messageDeque.push_back(msg);
   if (!writeInProgress)
   {
      boost::asio::async_write(*m_socket,
         boost::asio::buffer(m_messageDeque.front().GetData(),
         m_messageDeque.front().GetLength()),
          boost::bind(&GalconClient::handle_write, this,
            boost::asio::placeholders::error));
   }
}

void GalconClient::handle_write(const boost::system::error_code& error)
{
    if (!error)
    {
      m_messageDeque.pop_front();
      if (!m_messageDeque.empty())
      {
         boost::asio::async_write(*m_socket,
            boost::asio::buffer(m_messageDeque.front().GetData(),
            m_messageDeque.front().GetLength()),
            boost::bind(&GalconClient::handle_write, this,
            boost::asio::placeholders::error));
      }
    }
    else
    {
      closeSocket();
      m_connectFailed(*MessageBuilder::BuildErrorDescriptionMessage(error));
    }
}